// $codepro.audit.disable com.instantiations.assist.eclipse.analysis.deserializeabilitySecurity
package org.mysofts.eclipseman.archives;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.mysofts.eclipseman.exceptions.InstanciateArchiveException;
import org.mysofts.eclipseman.exceptions.UnsupportedFileTypeException;
import org.mysofts.eclipseman.util.AppLog;
import org.mysofts.eclipseman.util.MessageProvider;

/**
 * ArchiveFactory.
 * 
 * @author reda
 * 
 */
public final class ArchiveFactory {
	// [start] static fields
	/**
	 * Nom du package qui contient les classes qui gèrent les archives.
	 */
	private static String archivesPackageName;
	/**
	 * Map qui associe à chaque extension d'archive (avec le point) le type de
	 * classe qui la gère.
	 */
	private static Map<String, Class<? extends Archive>> mapExtensionsArchives;

	// [end]

	// [start] constructeurs
	/**
	 * Constructeur.
	 */
	private ArchiveFactory() { // $codepro.audit.disable emptyMethod

	}

	static {
		mapExtensionsArchives = new HashMap<String, Class<? extends Archive>>();
		archivesPackageName = StringUtils.substringBeforeLast(
				Archive.class.getCanonicalName(), ".");
		final Properties props = new Properties();
		try {
			props.load(ArchiveFactory.class
					.getResourceAsStream("archives.properties"));

			for (String suffix : props.stringPropertyNames()) {
				String simplename = props.getProperty(suffix);
				String classname = archivesPackageName + '.' + simplename;
				Exception logException = null;

				try {
					Class<?> clazz = Class.forName(classname);
					@SuppressWarnings("unchecked")
					Class<? extends Archive> trueClazz = (Class<? extends Archive>) clazz;
					mapExtensionsArchives.put(suffix.toLowerCase(), trueClazz);
				} catch (ClassNotFoundException e) { // $codepro.audit.disable
														// logExceptions
					logException = e;
				} catch (ClassCastException e) { // $codepro.audit.disable
													// logExceptions
					logException = e;
				}

				if (logException != null) {
					AppLog.warn(MessageFormat.format(MessageProvider
							.get("logs").sub("archive-factory").sub("static")
							.sub("errors").sub("invalid-class-name").value(),
							simplename, suffix), logException);
				} // end if
			}

		} catch (IOException e) { // $codepro.audit.disable logExceptions
			AppLog.warn(MessageProvider.get("logs").sub("archive-factory")
					.sub("static").sub("errors").sub("cant-read-properties")
					.value());
		}
	}

	// [end]

	/**
	 * Liste des extensions (avec le point) d'archives reconnues.
	 * 
	 * @return the archiveExtensions Liste des extensions (avec le point)
	 *         d'archives reconnues.
	 */
	public static String[] getArchiveExtensions() {
		return mapExtensionsArchives.keySet().toArray(
				new String[mapExtensionsArchives.keySet().size()]);
	}

	/**
	 * Instancie une archive à partir d'un fichier.
	 * 
	 * @param clazz
	 *            Type de classe de l'archive à instancier.
	 * @param file
	 *            Fichier archive.
	 * @param <TArchive>
	 *            Type d'archive à instancier.
	 * @return Archive instanciée.
	 * @throws InstanciateArchiveException
	 *             Si l'instanciation a échoué.
	 */
	private static <TArchive extends Archive> TArchive instanciate(
			final Class<TArchive> clazz, final File file)
			throws InstanciateArchiveException {
		Exception exceptionToLog = null;
		try {
			final Constructor<TArchive> cons = clazz.getConstructor(File.class);
			final TArchive instance = cons.newInstance(file);
			return instance;
		} catch (SecurityException e) { // $codepro.audit.disable logExceptions
			exceptionToLog = e;
		} catch (NoSuchMethodException e) { // $codepro.audit.disable
											// logExceptions
			exceptionToLog = e;
		} catch (IllegalArgumentException e) { // $codepro.audit.disable
												// logExceptions
			exceptionToLog = e;
		} catch (InstantiationException e) { // $codepro.audit.disable
												// logExceptions
			exceptionToLog = e;
		} catch (IllegalAccessException e) { // $codepro.audit.disable
												// logExceptions
			exceptionToLog = e;
		} catch (InvocationTargetException e) { // $codepro.audit.disable
												// logExceptions
			exceptionToLog = e;
		}

		throw new InstanciateArchiveException(file, clazz, exceptionToLog);
	}

	/**
	 * Crée un {@link Archive} à partir d'un fichier.
	 * 
	 * @param file
	 *            Fichier archive.
	 * @return Instance de {@link Archive} correspondante.
	 * @throws UnsupportedFileTypeException
	 *             Type d'archive non supporté.
	 * @throws InstanciateArchiveException
	 *             Erreur d'instanciation du manager.
	 */
	public static Archive createArchiveManager(final File file)
			throws UnsupportedFileTypeException, InstanciateArchiveException {
		int suffixLength = 0;
		Class<? extends Archive> choosedClazz = null;
		final String name = file.getName().toLowerCase();
		for (Map.Entry<String, Class<? extends Archive>> e : mapExtensionsArchives
				.entrySet()) {
			final String suffix = e.getKey();
			final Class<? extends Archive> clazz = e.getValue();
			if (name.endsWith(suffix) && (suffix.length() > suffixLength)) {
				suffixLength = suffix.length();
				choosedClazz = clazz;
			} // end if
		}

		if (choosedClazz == null) {
			throw new UnsupportedFileTypeException(file);
		} // end if
		return instanciate(choosedClazz, file);
	}

}
